El presente analisis se realiza sobre un conjunto de datos normalizados de precios, sucursales y productos derivado de datos obtenidos de la página de Precios Claros (http://preciosclaros.gob.ar/).
En las siguientes secciones se procedera a presentar la información del conjunto de datos. Identificaremos las variables más relevantes, sus relaciones y elaboraremos una conclusión al respecto.
%pylab inline
import sys
import pandas as pd
from io import StringIO
import numpy as np
import seaborn as sns
import scipy as sp
import scipy.stats as stats
import matplotlib.pyplot as plt
sns.set(style='ticks', color_codes=True)
sns.set_context(context='talk', font_scale=1.2)
in_colab = 'google.colab' in sys.modules
if 'sidetale' not in sys.modules:
!pip install sidetable
import sidetable
if in_colab:
from google.colab import files
# Install Kaggle library
if 'kaggle' not in sys.modules:
!pip install -q kaggle
#Definimos colores
BLUE = '#35A7FF'
RED = '#FF5964'
# Upload kaggle API key file only first time
if in_colab:
uploaded = files.upload()
#move kaggle.json API KEY file
if in_colab:
!mv kaggle.json /root/.kaggle
#change permission kaggle.json API KEY file
if in_colab:
!chmod 600 /root/.kaggle/kaggle.json
#!donwload dataset zip file to colab
if in_colab:
!kaggle datasets download -d darioyvanoff/preciosucursalproducto
#read dataset zip file
if in_colab:
file_name = 'preciosucursalproducto.zip'
else:
file_name = 'precio_sucursal_producto.pkl'
precio_sucursal_producto = pd.read_pickle(file_name)
precio_sucursal_producto.dtypes
precio_sucursal_producto.describe(include='all')
Basado en que el objetivo final de este proyecto es encontrar una forma de identificar si un producto está caro o barato, no en una sucursal puntual, sino dada una región o ciudad hemos decidido como variables relevantes:
precio_sucursal_producto[precio_sucursal_producto.region=='Patagonia'].nom_provincia.unique()
precio_sucursal_producto.head()
precio_sucursal_producto.shape
precio_sucursal_producto.isnull().sum().to_frame().sort_values(0, ascending = False).reset_index()
Como se observa en las variables de interes seleccionadas se encuentran 7623 NaN en la variable precio mientras que se ecuentran 20000 NaN en comercioRazonSocial, provincia y localidad.
Importante: Habida cuenta que las 7623 filas de precios NaN representan solo el 0.34% de los datos decidimos eliminarlos de nuestro conjunto de datos.
Generamos un dataset solo con las variables relevantes identificadas que se utilizará para las siguientes secciones de análisis
#Generamos un dataset con las variables de interes sin NaN en precios y convirtiendo la fecha a int64 para mejor tratamiento posterior
precio_no_nan=~precio_sucursal_producto.precio.isna()
dataset = precio_sucursal_producto[precio_no_nan]
datasetNew=dataset[['fecha','sucursal_id','producto_id','nombre_sin_um','localidad','nom_provincia','region','comercioRazonSocial','presentacion','precio','cantidad','unidad','marca']]
datasetNew=datasetNew.astype({'fecha': 'int64'})
datasetNew.shape
Nota: Luego de eliminar los NaN en la columna precio del dataset original obtenemos un conjunto de datos con 7623 filas menos ( 2222411 - 2214788) en nuestro conjunto de datos.
Chequeo NaN en variables de intereses de nuestro nuevo dataset
A continuación vamos a revisar de las variables de interes que valores quedaron en NaN luego de haber descartado las filas con NaN en la variable precio.
#Chequeoamos presencia de NaN en variables de interes
datasetNew.isnull().sum().to_frame().sort_values(0, ascending = False).reset_index()
Nota como podemos observar existen la misma cantidad de filas con comercioRazonSocial, region, nom_provincia y localidad en NaN. Dato el porcentaje bajo respecto a la muestra hemos decidido eliminar las filas en donde estas variables tengan NaN.
localidad_no_nan=~datasetNew.localidad.isna()
dataset_aux = datasetNew[localidad_no_nan]
datasetNew=dataset_aux[['fecha','sucursal_id','producto_id','nombre_sin_um','localidad','nom_provincia','region','comercioRazonSocial','presentacion','precio','cantidad','unidad','marca']]
datasetNew=datasetNew.astype({'fecha': 'int64'})
datasetNew.shape
#Chequeo de NaN en variables de interes
datasetNew.isnull().sum().to_frame().sort_values(0, ascending = False).reset_index()
A continuación procederemos a agregar dos nuevas variables las cuales son el precio por unidad de medida y la unidad de medida. Llevaremos gr a kg, cc y ml a lt y dejaremos tal cual estan un, mt, kg. El precio sera dividido por la cantidqd de manera de tener precios por unidades.
#Mostramos las unidades de medida existentes en el conjunto de datos
pd.Series(datasetNew.unidad.unique()).to_frame()
#Definimos una función para dividir precio por cantidad y unificar las unidades de medida
def precioXUnidad(cantidad,unidad,precio):
if unidad in ('un', 'mt','kg','lt'):
return round(precio/cantidad,2) , unidad
elif unidad in ('gr'):
return round(precio/cantidad*1000,2) , 'kg'
elif unidad in ('ml','cc'):
return round(precio/cantidad*1000,2) , 'lt'
#Creamos dos nuevas columnas denominadas PrecioXUnidad y nuevaUnidad para los valores "homogeneizados"
datasetNew[['PrecioXUnidad','nuevaUnidad']] = datasetNew.apply(lambda x: precioXUnidad(x['cantidad'],x['unidad'],x['precio']),axis=1, result_type='expand')
A modo de ejemplo mostramos la conversion de gramos a kg. Se agregaron dos nuevas columnas llamadas PrecioXUnidad y nuevaUnidad
datasetNew[datasetNew.unidad=='gr'].round(2)
Comenzamos mostrando los valores estadisticos de media, mediana, desvios entre otros de las variables de interes en el conjunto de datos
datasetNew.describe(include='all')
a- Outliers de variables seleccionadas
En esta sección se verificará la existencia de outliers en PrecioXUnidad. Para ello en primera medida se procedera a realizar representaciones gráficas a través de boxplot de manera de poder identificarlos.
plt.figure(figsize=(8,5))
p1=sns.boxplot(data=datasetNew, color=BLUE,
x='fecha', y='PrecioXUnidad')
plt.ylabel('precioXUnidad')
plt.xlabel('fecha')
plt.xticks(rotation=45)
sns.despine()
OBTENCIÓN DE OUTLIERS
A continuación obtenemos un dataset con media y desvios de cada producto a nivel nacional para luego obtener un dataset con todos los outliers que esten a mas de 3 desvios standares para cada fecha del conjunto de datos
#funcion para determinar el desvío estámdard poblacional
def pop_std(x):
return x.std(ddof=0)
# Obtenemos un dataset cde medias y desvios para fecha y producto
df_product_mean_std=datasetNew[['fecha','producto_id','PrecioXUnidad']].groupby(['fecha','producto_id'],as_index=False).agg(['mean',pop_std])
df_product_mean_std.columns = ['media','desvio']
df_product_mean_std = df_product_mean_std.reset_index()
df_product_mean_std.head()
#Unimos los dataset de medias y desvios al dataset original para obtener un dataset resultante con media y desvio de cada producto
datasetNew=pd.merge(datasetNew,df_product_mean_std)
datasetNew.head()
#Chequeoamos presencia de NaN en variables de interes
datasetNew.isnull().sum().to_frame().sort_values(0, ascending = False).reset_index()
Generamos un dataset con los outliers globales del dataset para verificar su tamaño
outliers=datasetNew[(datasetNew.PrecioXUnidad > (datasetNew.media + (3 * datasetNew.desvio))) | (datasetNew.PrecioXUnidad < (datasetNew.media - (3 * datasetNew.desvio)))]
outliers.head()
outliers.shape
datasetNew.shape
A continuación mostramos un ejemplo de outlier
datasetNew[(datasetNew.sucursal_id=='10-1-55') & (datasetNew.producto_id=='8711700045902')]
datasetNew[(datasetNew.producto_id=='8711700045902')].describe(include='all')
A continuación se muestra el número de outliers de PrecioXUnidad en todos los dias informados en el conjunto de datos por un lado, y luego por separado dia por dia por el otro.
#Vemos cuántos outliers presentan precio juntando todos los dias
print('Todos los dias juntos'
'\n Outliers de precio: %2i == %2.2f %% del total'
%(len(outliers), len(outliers)*100/float(len(datasetNew))))
print('')
#Un vistazo a la cantidad de outliers por dia
for i in datasetNew.fecha.unique():
Out_Glo_precio_dia = len(outliers[outliers.fecha == i])
print('Día %i'
'\n Outliers de precio: %2i == %2.2f %% del total'
%(i,
Out_Glo_precio_dia, Out_Glo_precio_dia*100/float(len(datasetNew[datasetNew.fecha == i]))))
Eliminación de outliers
A continuación se obtiene el dataset sin outlier eliminando los valores que se encuentran a mas de 3 desviíos standard de la media
datasetNew=datasetNew[(numpy.abs(datasetNew.PrecioXUnidad - datasetNew.media) <= (3 * datasetNew.desvio))]
datasetNew.shape
b- Dstribuciones de las variables de interés
Para una primera aproximación vamos a verificar la distrbución de las variable PrecioXUnidad en cada fecha disponible en el conjunto de datos
#Un vistazo a la cantidad de outliers por dia
bins_pf=15 #Bines para precio
plt.figure(figsize=(15,5))
plt.suptitle('Comparación visual entre distribuciones')
for i in datasetNew.fecha.unique():
dia = (datasetNew.fecha == i)
datasetNewAux=datasetNew[dia]
sns.distplot(datasetNewAux.PrecioXUnidad, kde=True, norm_hist=True, bins=bins_pf, label='País')
sns.lineplot(np.linspace(min(datasetNewAux.PrecioXUnidad), max(datasetNewAux.PrecioXUnidad), bins_pf),
sp.stats.norm.pdf(np.linspace(min(datasetNewAux.PrecioXUnidad), max(datasetNewAux.PrecioXUnidad), bins_pf),
datasetNewAux.PrecioXUnidad.mean(), datasetNewAux.PrecioXUnidad.std()), label='Gaussiana')
plt.xlabel('Precio dia %i' %i, weight='bold')
plt.ylabel('Frecuencia normalizada')
plt.xlim(min(datasetNew.PrecioXUnidad), max(datasetNew.PrecioXUnidad))
sns.despine()
plt.show()
Realizamos el test de Kolgomorov sobre la variable PrecioXunidad de todo el conjunto de datos
#Planteamos la primer hipótesis nula
print('Hipótesis nula: La distribución de Precios es Gaussiana con: '
'\n mu = %.3f'
'\n sigma = %.3f'
%(datasetNew.PrecioXUnidad.mean(), datasetNew.PrecioXUnidad.std())), print('')
#Para realizar el ks_test, se normalizan los datos: (x-mu)/sigma), para luego comparar con una distribución normal.
precio_LAC_normed = (datasetNew.PrecioXUnidad - datasetNew.PrecioXUnidad.mean())/datasetNew.PrecioXUnidad.std()
#Nivel de significancia
alpha_hf = 0.05
print('Definimos un nivel de significancia:'
'\n alpha =', alpha_hf), print('')
#Realizamos el ks_test
_, pvalue_hf = sp.stats.kstest(precio_LAC_normed, 'norm')
print('Realizamos el estadístico de prueba ks_test:'
'\n pvalue = %f' %pvalue_hf), print('')
#Vemos los resultados
if (alpha_hf>pvalue_hf):
print('Se rechaza la hipótesis nula con el nivel de significancia impuesto.')
else:
print('No se puede rechazar la hipótesis nula con el nivel de significancia impuesto.')
Nota: Basados en el test de Kolmogorov antes realizado podes concluir que los PrecioXUnidad no tienen una distribución gausiana.
Para una segunda aproximación procedemos ahora a verificar la distribución de PrecioXUnidad por rangos de valores y fechas
#Agregamos una nueva columna para determinar rangos de precios
datasetNew['rango'] = pd.cut(datasetNew.PrecioXUnidad, bins= [0, 1500, 3000, 100000000], labels=["low", "medium", "high"], right=False)
#Chequeoamos presencia de NaN en variables de interes
datasetNew.isnull().sum().to_frame().sort_values(0, ascending = False).reset_index()
#Imprimimos el máximo valor de PrecioXUnidad de cada rango
print("precio maximo segmento low %.f" % datasetNew[datasetNew.rango=='low'].PrecioXUnidad.max())
print("precio maximo segmento medium %.f" % datasetNew[datasetNew.rango=='medium'].PrecioXUnidad.max())
print("precio maximo segmento high %.f" % datasetNew[datasetNew.rango=='high'].PrecioXUnidad.max())
#Un vistazo alas distribuciones por dia y rango de precios
bins_pf=15 #Bines para precio
plt.suptitle('Comparación visual entre distribuciones')
for i in pd.Series(datasetNew.fecha.unique()).sort_values():
dia = (datasetNew.fecha == i)
datasetNewAux=datasetNew[dia]
for j in datasetNewAux.rango.unique():
rango= (datasetNewAux.rango==j)
datasetNewQ=datasetNewAux[rango]
sns.distplot(datasetNewQ.PrecioXUnidad, kde=True, norm_hist=True, bins=bins_pf, label='País')
sns.lineplot(np.linspace(min(datasetNewQ.PrecioXUnidad), max(datasetNewQ.PrecioXUnidad), bins_pf),
sp.stats.norm.pdf(np.linspace(min(datasetNewQ.PrecioXUnidad), max(datasetNewQ.PrecioXUnidad), bins_pf),
datasetNewQ.PrecioXUnidad.mean(), datasetNewQ.PrecioXUnidad.std()), label='Gaussiana')
plt.xlabel('Precio dia %i' %i+' Categoría %s' %j,weight='bold')
plt.ylabel('Frecuencia normalizada')
plt.xlim(min(datasetNewQ.PrecioXUnidad), max(datasetNewQ.PrecioXUnidad))
sns.despine()
plt.show()
Nota: como se observa tomando los rangos de PrecioXUnidad las distribuciones presentan asimetrica con cola hacia la derecha (asimetria positiva)
Ahora Utilizamreos los quartiles para dividir en 4 rangos los precios y tener una forma alternativa de verificar la distribución de los precios
#Categorizamos en una nueva columna el cuartil a la que pertenece cada precio para obtener rangos
quartiles = pd.qcut(datasetNew['PrecioXUnidad'], 4, labels=range(1,5))
datasetNew = datasetNew.assign(quartil=quartiles.values)
#Un vistazo a la dsitribuciones por dia y quartiles de precios
bins_pf=15 #Bines para precio
plt.suptitle('Comparación visual entre distribuciones')
for i in pd.Series(datasetNew.fecha.unique()).sort_values():
dia = (datasetNew.fecha == i)
datasetNewAux=datasetNew[dia]
for j in datasetNewAux.quartil.unique().sort_values():
quartil= (datasetNewAux.quartil==j)
datasetNewQ=datasetNewAux[quartil]
sns.distplot(datasetNewQ.PrecioXUnidad, kde=True, norm_hist=True, bins=bins_pf, label='País')
sns.lineplot(np.linspace(min(datasetNewQ.PrecioXUnidad), max(datasetNewQ.PrecioXUnidad), bins_pf),
sp.stats.norm.pdf(np.linspace(min(datasetNewQ.PrecioXUnidad), max(datasetNewQ.PrecioXUnidad), bins_pf),
datasetNewQ.PrecioXUnidad.mean(), datasetNewQ.PrecioXUnidad.std()), label='Gaussiana')
plt.xlabel('Precio dia %i' %i+' Quartil %i' %j,weight='bold')
plt.ylabel('Frecuencia normalizada')
plt.xlim(min(datasetNewQ.PrecioXUnidad), max(datasetNewQ.PrecioXUnidad))
sns.despine()
plt.show()
Nota: si observamos los cuartiles tambien se puede verificar una distribución asimétrica positiva en los precios como la que se observo con mayor detalle por rangos de precios antes realizada.
Realizamos el test de Kolmogorov para el día 20200412 solo a modo de reconfirmar que los precios no siguen una distribución normal
#Planteamos la primer hipótesis nula
print('Hipótesis nula: La distribución de Precios es Gaussiana con: '
'\n mu = %.3f'
'\n sigma = %.3f'
%(datasetNew[datasetNew.fecha==20200412].PrecioXUnidad.mean(), datasetNew[datasetNew.fecha==20200412].PrecioXUnidad.std())), print('')
#Para realizar el ks_test, se normalizan los datos: (x-mu)/sigma), para luego comparar con una distribución normal.
precio_LAC_normed = (datasetNew[datasetNew.fecha==20200412].PrecioXUnidad - datasetNew[datasetNew.fecha==20200412].PrecioXUnidad.mean())/datasetNew[datasetNew.fecha==20200412].PrecioXUnidad.std()
#Nivel de significancia
alpha_hf = 0.05
print('Definimos un nivel de significancia:'
'\n alpha =', alpha_hf), print('')
#Realizamos el ks_test
_, pvalue_hf = sp.stats.kstest(precio_LAC_normed, 'norm')
print('Realizamos el estadístico de prueba ks_test:'
'\n pvalue = %f' %pvalue_hf), print('')
#Vemos los resultados
if (alpha_hf>pvalue_hf):
print('Se rechaza la hipótesis nula con el nivel de significancia impuesto.')
else:
print('No se puede rechazar la hipótesis nula con el nivel de significancia impuesto.')
Nota: Basados en el test de Kolmogorov antes realizado podes concluir que los precios no tienen una distribución gausiana.
En base a los test realizados podemos concluir que las distribuciones de los precios no responden a la distribución gausiana
c- Frecuencia de variables categóricas de interés
Generamos la tabla de Frecuencia de las variables de interés
datasetNew.describe(exclude=[np.number])
datasetNew.stb.freq(['fecha', 'region'])
datasetNew.stb.freq(['fecha', 'nombre_sin_um'])
datasetNew.stb.freq(['fecha', 'nom_provincia'])
datasetNew.stb.freq(['fecha', 'comercioRazonSocial'])
#Mostramos el número de sucursales por nombre de provincia
datasetNew.groupby(['fecha','nom_provincia'])['sucursal_id'].nunique().sort_values(ascending=False).to_frame()
Como se observa las sucursales que informan precios no lo hacen en todas las fechas. Hay sucursales que no informan precios para ciertas fechas en el conjunto de datos.Por ejemplo, para el 20200412 hay 100 sucursales informando precios pero para el día 20200426 hay solo 51.
d- Distribución de precios condicionada a variables categóricas
Distribución por quartiles y por día
matplotlib.rc('figure', figsize=(10, 5))
for i in pd.Series(datasetNew.fecha.unique()).sort_values():
dia = (datasetNew.fecha == i)
datasetNewAux=datasetNew[dia]
plot = pd.crosstab(index=datasetNewAux['region'],
columns=datasetNewAux['quartil']
).apply(lambda r: r/r.sum() *100, axis=1).plot(kind='barh', stacked=True)
plot.set_xlabel("Fecha %i" %i)
plot.set_ylabel("Regiones")
plot.legend(loc='center left',bbox_to_anchor=(1.0, 0.5),title='Quartil')
Distribución por rangos y por día
matplotlib.rc('figure', figsize=(10, 5))
for i in pd.Series(datasetNew.fecha.unique()).sort_values():
dia = (datasetNew.fecha == i)
datasetNewAux=datasetNew[dia]
plot = pd.crosstab(index=datasetNewAux['region'],
columns=datasetNewAux['rango']
).apply(lambda r: r/r.sum() *100, axis=1).plot(kind='barh', stacked=True)
plot.set_xlabel("Fecha %i" %i)
plot.set_ylabel("Regiones")
plot.legend(loc='center left',bbox_to_anchor=(1.0, 0.5),title='Rango')
Identificación de un bien númerico
A fin de poder tener una comparativa de precios relativos procederemos a identificar un producto que se encuentre en todas las provincias
prod_region_count = datasetNew.groupby(['producto_id','nom_provincia' ])['producto_id'].nunique().groupby('producto_id').count().reset_index(name='count')
bienNumerico=prod_region_count[(prod_region_count['count']==24)]
bienNumerico.head()
#Producto utilizado para bien numerido = Crema Dental Interdental Colgate
#Verificamos si el bien numérico se encuentra entre los productos que se encuentran en todas las regiones
idProducto='7793100111563'
bienNumerico[bienNumerico.producto_id==idProducto]
#Muestramos el nombre del producto seleccionado como bien numérico
datasetNew[datasetNew.producto_id==idProducto]
Para el producto seleccionado como bien numérico obtenemos el promedio de PrecioXUnidad agrupado por fecha y nombre de provincia
#Armamos un dataset con la media de PrecioXUnidad por fecha y provincia del bien numerico seleccionado
dfbn=datasetNew[(datasetNew.producto_id==idProducto)][['fecha','nom_provincia','PrecioXUnidad']].groupby(['fecha','nom_provincia'],as_index=False).agg(['mean'])
dfbn.columns = ['bienNumerico']
dfbn = dfbn.reset_index()
dfbn
Agregamos el precio relativo de cada producto al conjunto de datos
#Agregamos al dataset una nueva columna con el precio relativo
datasetPR=pd.merge(left=datasetNew,right=dfbn,how='inner',left_on=['fecha','nom_provincia'],right_on =['fecha','nom_provincia'])
datasetPR['precioRelativo']=datasetPR.PrecioXUnidad/datasetPR.bienNumerico
datasetPR.head()
datasetPR.shape
#Verificamos que en el merge left join no perdemos datos respecto del conjunto de datos original y a partir
#de este momento ttrabajamos con datasetPR
len(datasetNew)-len(datasetPR)
datasetPR.isna().sum().to_frame().sort_values(0, ascending = False).reset_index()
Análisis de dispersión del bien numérico en todas las suursales
datasetNew[datasetNew.producto_id==idProducto]
bins_pf=15 #Bines para precio
matplotlib.rc('figure', figsize=(10, 5))
datasetNewAux=datasetNew[datasetNew.producto_id==idProducto]
for i in pd.Series(datasetNewAux.fecha.unique()).sort_values():
dia = (datasetNewAux.fecha == i)
datasetScatter=datasetNewAux[dia]
plt.scatter(datasetScatter.index, datasetScatter.PrecioXUnidad, color="b")
plt.suptitle('Dispersión del bien numérico en todas las sucursales dia %i' %i)
plt.xlabel('Indice',weight='bold')
plt.ylabel('PrecioXunidad')
sns.despine()
plt.show()
Como se observa para el bien numérico selecionado para todos los dias los valores informados en las sucursales a nivel país oscilan entre $700 a $1200
Análisis de correlaciones
region_rango_pivot = datasetNew.groupby(['region','rango']).size().reset_index(name='counts').pivot('region', 'rango', 'counts')
plt.figure(figsize=(10,8))
hm01 = sns.heatmap(region_rango_pivot, annot=True, fmt='d', annot_kws={"size":14}, cmap="Blues")
hm01.invert_yaxis()
plt.title("Heatmap \n Region y Rango de Precios")
plt.xlabel("Rango", weight='bold')
plt.ylabel("Region", weight='bold')
plt.show()
Como se observa en el mapa de calor en todas las regiones la mayor cantidad de productos informados pertenecen al rango bajo de PrecioXUnidad para todo el conjunto de datos.
provincia_rango_pivot = datasetNew.groupby(['nom_provincia','rango']).size().reset_index(name='counts').pivot('nom_provincia', 'rango', 'counts')
plt.figure(figsize=(15,8))
hm01 = sns.heatmap(provincia_rango_pivot, annot=True, fmt='.1f', annot_kws={"size":15}, cmap="Blues")
hm01.invert_yaxis()
plt.title("Heatmap \n Provincia y Rango de Precios")
plt.xlabel("Rango", weight='bold')
plt.ylabel("Provincia", weight='bold')
plt.show()
Como se observa en el mapa de calor en todas las provincias también la mayor cantidad de productos informados pertenecen al rango bajo de PrecioXUnidad para todo el conjunto de datos.
negocio_rango_pivot = datasetNew.groupby(['comercioRazonSocial','rango']).size().reset_index(name='counts').pivot('comercioRazonSocial', 'rango', 'counts').dropna()
plt.figure(figsize=(10,12))
hm01 = sns.heatmap(negocio_rango_pivot, annot=True, fmt='.1f', annot_kws={"size":20}, cmap="Blues")
hm01.invert_yaxis()
plt.title("Heatmap \n Negocio y Rango de Precios")
plt.xlabel("Rango", weight='bold')
plt.ylabel("Negocio", weight='bold')
plt.show()
Como se observa Josimar S.A. es el que presenta mayor cantidad de productos informados en el rango de precios bajos.
3-RESPUESTAS OBLIGATORIAS
Cuál es la correlación entre precios nominales y relativos? Nos sirve de algo este dato?
Los precios relativos están constituidos a partir de los precios nomiales dividido el precio de un bien numérico existente en las 24 provincias para todos los dias del dataset. La variacion positiva o negativa de un precio relativo en el tiempo nos da una dimensión del costo de oportunidad de resignar la compra del bien numerico en pos de comprar otro producto.
Qué productos están en todas las provincias?
#Productos que se encuentran en todas las provincias
prod_provincia_count = datasetPR.groupby(['nom_provincia','nombre_sin_um' ])['nombre_sin_um'].nunique().groupby('nombre_sin_um').count().reset_index(name='count')
prodProvincia=prod_provincia_count[(prod_provincia_count['count']==24)]
prodProvincia.head(10)
Cómo podemos medir la dispersión de precios? Encontrar una métrica y un elemento de visualización.
La dispersión de percios se puede medir de diferentes formas. Una forma sería teniendo en cuenta sus medias y desvios. Aquellos productos que tengan un desvío mayor tendrán mayor dispersión. Si tomamos en cuenta 3 desvios estandar de la media podríamos encontrar los rangos entre los valores mínimos y máximos sin outlier que representan el rango de precios para un producto determinado. Otra forma podría ser teniendo en cuenta quartiles. En ese sentido el grafico de boxplot es una buena opción para verificar dispersión. A continuación se presenta el boxplot del producto que tomamos como bien númerico por fecha y por región.
dfbienNumerico=datasetPR[datasetPR.producto_id==idProducto]
OrdReg = dfbienNumerico.groupby('region').mean().reset_index().sort_values('precio',ascending=False)['region']
for i in pd.Series(dfbienNumerico.fecha.unique()).sort_values():
dia = (datasetNew.fecha == i)
datasetNewAux=datasetNew[dia]
plt.figure(figsize=(10,5))
plt.suptitle('Precio por Región Fecha %i' %i+' Id Producto '+idProducto, y=1.02)
sns.boxplot(data=datasetNewAux,
x='precio', y='region', color=BLUE, order=OrdReg)
plt.xlabel('')
plt.ylabel('')
plt.subplots_adjust(wspace=0.1)
plt.xlim(0,650)
sns.despine()
Como se observa para el análisis de boxplot del bien numérico que se acaba de graficar los precios con mayor dispersión, mediana y valores máximos corresponden a la región Patagonia seguido por Cuyo.
Qué tipos de productos se encuentran entre los más caros? En qué provincia se encuentran mayoritariamente?
datasetPR.sort_values(['PrecioXUnidad'], ascending=[False])
Encontramos precios excesivamente altos en chocolate y mascaras para pestañas lo cual nos hace suponer que podría haber datos erroneos en la presentación de estos productos. Sacandondolos del análisis, se observa que los productos con mayores valores corresponden a bebidas alcholicas y principalmente se encuentran en la región Centro en la provincia de Buenos Aires.
Qué tipos de productos se encuentran entre los más baratos? En qué provincia se encuentran mayoritariamente?
datasetPR.sort_values(['PrecioXUnidad'], ascending=[True])
Entre los más baratos se encuentran acondicionadores y shanpoo en la Provincia de Buenos Aires.
Cómo se distribuyen los precios por provincia y cadena de proveedor?
#Distribución del precio en cuartiles por fecha para cada provincia
matplotlib.rc('figure', figsize=(15, 8))
for i in pd.Series(datasetNew.fecha.unique()).sort_values():
dia = (datasetNew.fecha == i)
datasetNewAux=datasetNew[dia]
plot = pd.crosstab(index=datasetNewAux['nom_provincia'],
columns=datasetNewAux['quartil']
).apply(lambda r: r/r.sum() *100, axis=1).plot(kind='barh', stacked=True)
plot.set_xlabel("Fecha %i" %i)
plot.set_ylabel("provincia")
plot.legend(loc='center left',bbox_to_anchor=(1.0, 0.5),title='Quartil')
Nota: Los gráficos representan la distribución del precio en quartiles para cada provincia en una fecha determinada.
#Distribución del precio en quartiles por fecha en cada comercio
matplotlib.rc('figure', figsize=(15, 12))
for i in pd.Series(datasetNew.fecha.unique()).sort_values():
dia = (datasetNew.fecha == i)
datasetNewAux=datasetNew[dia]
plot = pd.crosstab(index=datasetNewAux['comercioRazonSocial'],
columns=datasetNewAux['quartil']
).apply(lambda r: r/r.sum() *100, axis=1).plot(kind='barh', stacked=True)
plot.set_xlabel("Fecha %i" %i)
plot.set_ylabel("")
plot.legend(loc='center left',bbox_to_anchor=(1.0, 0.5),title='Quartil')
Nota: Los gráficos representan la distribución del precio en quartiles para cada comercio en una fecha determinada.
Cuál es la región/provincia con mayor promedio de precios? Y de menor promedio?
#Armamos un dataset con los productos que se encuentran en todas las provincias y por ende en todas las regiones
datasetAllRegion=pd.merge(datasetPR, bienNumerico, on='producto_id',how='inner')
datasetAllRegion.head()
# Calculo la media de PrecioXUnidad por nombre de provincia
columnasimpor=['fecha','nom_provincia','PrecioXUnidad']
datasetPRMedia=datasetAllRegion[columnasimpor].groupby(['fecha','nom_provincia'],as_index=False).mean()
datasetPRMedia.columns=['fecha','nom_provincia','regionprovmean']
datasetPRMedia.groupby(['fecha']).max()
La provincia con mayor promedio de precios es Tucumán.
datasetPRMedia.groupby(['fecha']).min()
La provincia con menor promedio de precios es Catamarca.
Son estadísticamente distintas las medias de los precios entre dos regiones (las que elijan)?
# Calculo la media de PrecioXUnidad por region
columnasimpor=['fecha','region','PrecioXUnidad' ]
datasetPRMediaReg=datasetAllRegion[columnasimpor].groupby(['fecha','region'],as_index=False).mean()
datasetPRMediaReg.columns=['fecha','region','regionmean']
datasetPRMediaReg
sns.catplot(x="fecha", y='regionmean', hue='region',
data=datasetPRMediaReg,
kind="bar", height=5, aspect=3, palette=sns.color_palette("Blues_r"))
plt.xlabel('')
plt.ylabel('Media de Precios', weight='bold')
plt.xlabel('Día')
plt.ylabel('Media de Precios', weight='bold')
plt.show()
Como se puede observar en el gráfico anterior las medias de precios a nivel region por fecha son distintas entre regiones.
Cuál es la probabilidad de que un precio en la provincia de Córdoba se encuentre por arriba de la media nacional?
# Obtenemos un dataset cde medias de precios de Córdoba por fecha
df_product_mean_cba=datasetNew[datasetNew.nom_provincia=='Córdoba']
df_product_mean_cba=df_product_mean_cba[['fecha','producto_id','PrecioXUnidad']].groupby(['fecha','producto_id'],as_index=False).agg(['mean'])
df_product_mean_cba.columns = ['mediacba']
df_product_mean_cba = df_product_mean_cba.reset_index()
df_product_mean_cba.head()
datasetNewPais=pd.merge(datasetNew,df_product_mean_cba,on=['fecha','producto_id'])
datasetNewPais.head()
print('Todos los dias juntos'
'\n Porcentaje de precios de Córdoba cuya media esta por debajo la media país: %2i == %2.2f %% del total'
%(len(datasetNewPais[datasetNewPais.media>datasetNewPais.mediacba]), len(datasetNewPais[datasetNewPais.media>datasetNewPais.mediacba])*100/float(len(datasetNewPais))))
print('')
for i in datasetNewPais.fecha.unique():
precio_dia = len(datasetNewPais[(datasetNewPais.media>datasetNewPais.mediacba) & (datasetNewPais.fecha == i)])
print('Día %i'
'\n Porcentaje de precios de Córdoba cuya media esta por debajo de la media país de precio: %2i == %2.2f %% del total'
%(i,
precio_dia, precio_dia*100/float(len(datasetNewPais[datasetNewPais.fecha == i]))))
Nota: en general podemos decir que la probabilidad de que la media de un producto en Córdoba sea menor que la media del mismo producto a nivel país es cercana al 70%.
A modo de ayuda, se propone un listado de preguntas adicionales. Podrán hacer uso de las mismas, aunque no es estrictamente necesario que las respondan. Algunas pueden resultarles de mayor interés que otras.
- Se puede obtener de alguna forma la categoría de los productos a partir de sus nombres
Se puede obtener de diferentes maneras, pero se esta investigando la menera de poder caracterizar con el proceso "Name Entity Recognition with NLTK SpaCy". Las categoría es una rama importante para el análisis de los productos. Por otro lado los productos se dividieron anteriormente por los percentiles sobre los precios y también se generó una selección por rangos de precios.
- Qué productos no están en todas las provincias?
datasetPR_f = datasetPR.groupby('producto_id')[['fecha']].nunique().reset_index()
datasetPR_f = datasetPR_f[datasetPR_f.fecha == datasetNew.fecha.nunique()]
datasetPR_f.head()
#Genero un dataFrame para contar los productos que aparecen en cada fecha y en cada provincia
datasetPR_new = datasetPR.groupby('producto_id')[['fecha','nom_provincia']].nunique().reset_index()
#Comparó para poder determinar que pertenencen a todas las fechas y a todas las provincias, con los valores unicos del dataFrame orginal.
datasetPR_new = datasetPR_new[(datasetPR_new.fecha == datasetNew.fecha.nunique()) & (datasetPR_new.nom_provincia == datasetNew.nom_provincia.nunique())]
#Creo una lista para poder mejorar la performance de mi función
lista_prod_unicos = list(datasetPR_new.producto_id)
#Agrego a mi dataFrame una columna para determinar si ese producto pertenece o no pertenece a todas las provincias y a todas las fechas.
datasetPR['pertenece_all_fecha_provincia'] = [True if x in lista_prod_unicos else False for x in list(datasetPR.producto_id)]
#Listo los productos que no estan en todas las provincias
lista_prod = list(datasetPR.nombre_sin_um[datasetPR.pertenece_all_fecha_provincia == False].unique())
lista_prod[:5]
- Qué marcas no están en todas las provincias?
#En este caso tuve que utilizar un dataFrame diferente porque no tiene la marcas correspondientes.
datasetPR_new = datasetPR.groupby(['marca'])[['fecha','nom_provincia']].nunique().reset_index()
datasetPR_new = datasetPR_new[(datasetPR_new.nom_provincia == datasetNew.nom_provincia.nunique()) & (datasetPR_new.fecha == datasetNew.fecha.nunique()) ]
lista_prod_marcas = list(datasetPR_new.marca.unique())
#len(lista_prod_marcas)#371
#len(datasetPR.marca)
#Agrego la marca al dataframe para pode ejecutar mi proceso de identificar las marcas que no estan en todas las provincias.
datasetPR['pertenece_all_fecha_provincia_marca'] = datasetPR.marca.apply(lambda x: True if x in lista_prod_marcas else False) #[True if x in lista_prod_marcas else False for x in list(datasetPR_new.marca)]
list_marcas = list(datasetPR.marca[datasetPR.pertenece_all_fecha_provincia_marca == False].unique())
list_marcas[:5]
- Son variables independientes las provincias y las cadenas de supermercados?
data_supermercado = datasetPR.comercioRazonSocial.value_counts(normalize=True)
data_supermercado
data_provincia = datasetPR.nom_provincia.value_counts(normalize=True)
data_provincia
plt.figure(figsize=(15, 6))
sns.barplot(x=data_supermercado.index, y=data_supermercado.values, color='steelblue' )
plt.xticks(rotation=90)
plt.show()
plt.figure(figsize=(15, 6))
sns.barplot(x=data_provincia.index, y=data_provincia.values, color='steelblue' )
plt.xticks(rotation=90)
plt.show()
data_grafico = datasetPR.groupby(['nom_provincia','comercioRazonSocial']).count().reset_index()#nunique()
plt.figure(figsize=(15, 6))
sns.barplot(data = data_grafico ,x='comercioRazonSocial', y='producto_id', color='steelblue' )
plt.xticks(rotation=90)
plt.show()
matplotlib.rc('figure', figsize=(20, 10))
plot = pd.crosstab(index= datasetPR['comercioRazonSocial'],
columns= datasetPR['nom_provincia'],normalize=True
).apply(lambda r: r/r.sum() *100,axis=0).plot(kind='bar', stacked=True )
plot.legend(loc="center right" , bbox_to_anchor=(1.5, 0.05))
#Generamos un crosstab entre la variable categorica provincia y la variable supermercado
datasetPR_tab = pd.crosstab(datasetPR.nom_provincia, datasetPR.comercioRazonSocial, margins = True)
#Generamos un crosstab entre la variable categorica provincia y la variable supermercado
#datasetPR_tab = pd.crosstab(datasetPR.nom_provincia, datasetPR.comercioRazonSocial, margins = True).reset_index()
#datasetPR_tab
datasetPR_tab.columns = ['S.A. Importadora y Exportadora de la Patagonia',
'Jumbo Retail Argentina S.A.',
'INC S.A.',
'DIA Argentina S.A',
'Libertad S.A',
'Autoservicio La Amistad Cooperativa de Trabajo Limitada',
'Coto Centro Integral de Comercialización S.A.',
'Cooperativa Obrera Limitada de Consumo y Vivienda',
'Alberdi S.A.',
'Cooperativa Agrícola Ganadera e Industrial de Patagones y Viedma LTA.',
'Wal Mart Argentina S.R.L.',
'Aiello Supermercados S.A.',
'California S.A.',
'Cyre S.A.',
'Miguel Becerra S.A.',
'Supermercado El Nene S.A.',
'Almacenes Pampas S.A. ',
'Almacenes de Marca S.A.',
'Mariano S.A.',
'La Agricola Regional Cooperativa Limitada',
'Josimar S.A.',
'SUPERCLC S.A.',
'Deheza S.A.I.C.F. e I.',
'Estación Lima S.A.',
'ARNALDO P. APPELLA S.A.',
'SUPERMERCADO TUTI 2 S.A.',
'Farmacity S.A.',
'Asociación Mutual Socios y Adherentes de la Cooperativa Agrícola de Capitán Sarmiento',
'Millan S.A.',
'Pan American Energy LLC Argentina',
'Supermercados Toledo S.A.',
'Dinosaurio S.A.',
'Operadora de Estaciones de Servicios S.A.',
'row_totals']
datasetPR_tab.index = ['Catamarca',
'Chaco',
'Chubut',
'Ciudad Autónoma de Buenos Aires',
'Corrientes',
'Córdoba',
'Entre Ríos',
'Formosa',
'Jujuy',
'La Pampa',
'La Rioja',
'Mendoza',
'Misiones',
'Neuquén',
'Provincia de Buenos Aires',
'Río Negro',
'Salta',
'San Juan',
'San Luis',
'Santa Cruz',
'Santa Fe',
'Santiago del Estero',
'Tierra del Fuego',
'Tucumán',
'All']
datasetPR_tab.columns
#Generamos la tabla de valores obtenidos sin las filas y columnas de totales
datasetPR_tab_new = datasetPR_tab.drop(['row_totals'], axis=1)
datasetPR_tab_new=datasetPR_tab_new[:-1]
datasetPR_tab_new
chi2_result,p_value,df_value,table_expected=sp.stats.chi2_contingency(observed=datasetPR_tab_new)
confidence_level = 0.95
crit_value = sp.stats.chi2.ppf(q = confidence_level, df=datasetPR_tab_new)
print()
#print ('Resultados Tabla de Contingencia')
#print ('====================================')
#print(table_expected)
#print()
print ('chi2:', chi2_result)
print ('p-value= %.5f'% p_value)
print ('df = ', df_value)
#print('Valor Crítico: ',crit_value)
Nota: Como se puede observar en el p-valor obtenido las variables provincia y cadena son dependientes dado que el valor es menor que el valor de significancia y por tanto se rechaza la hipotesis nula.
- Conclusiones Adionales del trabajo realizado
A continuacion presentamos informmación que determinan sobre el conjunto de datos que región es mas barata con respecto a otras utilizando la mediana
# Calculo la mediana de precio de producto por región agregando la columna nueva
columnasimpor=['fecha','region','PrecioXUnidad' ]
datasetPRMediana=datasetAllRegion[columnasimpor].groupby(['fecha', 'region'],as_index=False).median()
datasetPRMediana.columns=['fecha','region','regionmean']
datasetPRMediana
sns.catplot(x="fecha", y='regionmean', hue='region',
data=datasetPRMediana,
kind="bar", height=5, aspect=3, palette=sns.color_palette("Blues_r"))
plt.xlabel('')
plt.ylabel('Precio', weight='bold')
plt.xlabel('Día')
plt.ylabel('Precio', weight='bold')
plt.show()